{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Topological Searches in pandapower"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is an introduction into the pandapower module topology. The topology module provides topoligical searches and analyses for pandapower networks based on the NetworkX library. This tutorial will show you how to get started and demonstrate a few use cases of the module. For a full documentation of the topology functions, see the pandapower documentation.\n",
    "\n",
    "To demonstrate the usage of the topology module we will use the medium voltage open ring from the simple pandapower test networks:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandapower.networks as nw\n",
    "net = nw.simple_mv_open_ring_net()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating MultiGraphs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The pandapower topology package provides a function to translate a pandapower network into an [networkx multigraph](https://networkx.github.io/documentation/networkx-1.9.1/reference/classes.multigraph.html):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandapower.topology as top\n",
    "mg = top.create_nxgraph(net) # converts example network into a MultiGraph "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This picture visualises the conversion: On the left hand side you can see what our example network looks like, on the right hand side how it gets converted into a MultiGraph.\n",
    "\n",
    "<img src=\"pics/multigraph_example.png\">\n",
    "    \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Algorithms from the NetworkX package\n",
    "\n",
    "The bus numbers in the networkx graph are the same as the bus indices in pandapower. You can now use all [networkx algorithms](https://networkx.github.io/documentation/networkx-1.9.1/reference/algorithms.html) to perform graph searches on the network."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### shortest path"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To find the [shortest path](https://networkx.github.io/documentation/networkx-1.9.1/reference/algorithms.shortest_paths.html) between nodes bus0 and bus5:\n",
    "\n",
    "<img src=\"pics/nx_shortest_path.png\">"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import networkx as nx\n",
    "path = nx.shortest_path(mg, 0, 5)\n",
    "path"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will algorithm will find the shortest path in terms of number of visited buses. The length of the lines is encoded in the weight parameter of the edges. If we want to find the shortest path in terms of shortest line length, we have to pass the weight parameter to the search:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "path = nx.shortest_path(mg, 0, 5, weight=\"weight\")\n",
    "path"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case the search of course yields the same path, since there is only one path from bus0 to bus5.\n",
    "\n",
    "Since the bus indices in the graph and in the pandapower network are the same, we can use the path to directly access buses in pandapower:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.bus.loc[path]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "gives us all buses on the shortest path between bus0 and bus5. We can also use the bus indices to find branch elements directly in pandapower. For example, to find all lines on the path:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.line.loc[top.elements_on_path(mg, path, \"line\")]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "or all transformers on this path:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.trafo.loc[top.elements_on_path(mg, path, \"trafo\")]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### customizing graph conversion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now suppose we want to find the shortest path distance between bus2 and bus6 without going through the transformer substation, but allowing to go over open switches. The path we are looking for is therefore bus6 --> bus5 --> bus4 --> bus3 --> bus2.\n",
    "\n",
    "This is not a path in the graph above though, since there is no edge between bus4 and bus5. We translate the graph with respect_switches=False to include the line with an open switch in the graph:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, respect_switches=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"pics/multigraph_example_respect_switches.png\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we still have the problem that the shortest path algorithm will find the path over the substation (bus1) as the shortest path:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nx.shortest_path(mg, 6, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To prevent this, we can specify bus1 as a nogobus in the conversion, which means it will not be translated to the networkx graph:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, respect_switches=False, nogobuses={1})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we get the path that we were looking for:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nx.shortest_path(mg, 6, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### cycles\n",
    "\n",
    "We can also use the [cycle algorithms](https://networkx.github.io/documentation/networkx-1.9.1/reference/algorithms.cycles.html) to find cycles in the network. Cycle algorithms only work on undirected graphs, which is why we need to specify multi=False in the graph conversion:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, multi=False)\n",
    "nx.cycle_basis(mg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are no cycles in the network, which confirms the radiality of the network. If we do not respect the switches, we will find the ring as a cycle:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, respect_switches=False, multi=False)\n",
    "nx.cycle_basis(mg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Algorithms in the topology package"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Besides from using networkx algorithms, there are some custom algorithms in the pandapower.topology package. For a full list with explanation see the pandapower documentation. Here, we only cover the two most important ones: connected_component and connected_components."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Connected component"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The connected component function returns all buses that are connected to a bus in the networkx graph. Suppose we want to find all buses that are on the same feeder as bus 2. We set bus1 as a nogobus and search for all buses connected to bus 2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, nogobuses={1})\n",
    "area = top.connected_component(mg, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This generator contains all buses connected to bus2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set(area)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We get the buses 2,3 and 4, but not bus1, since it was defined as a nogobus. If we want to get bus1 as connected to bus2, but still not go over bus2, we can define bus1 as a notravbus instead of a nogobus. This means that search algorithms will find the bus as connected, but not traverse it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, notravbuses={1})\n",
    "set(top.connected_component(mg, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Connected components"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we don't want to find the area connected to one specific bus, but rather all areas that are connected, we can use the connected_components function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, nogobuses={0, 1})\n",
    "for area in top.connected_components(mg):\n",
    "    print(area)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once again, we can alternatively use notravbuses to get the substation bus in the areas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, nogobuses={0}, notravbuses={1})\n",
    "for area in top.connected_components(mg):\n",
    "    print(area)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we want to avoid getting the notravbus as an own area, we can alternatively pass the notravbuses argument directly to the connected_components search instead of to the graph conversion:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mg = top.create_nxgraph(net, nogobuses={0})\n",
    "for area in top.connected_components(mg, notravbuses={1}):\n",
    "    print(area)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more examples of topological searches in pandapower, see the documentation of the topology package."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
